import { errorFactory, rejectNull, rejectNotNull } from '../../infrastructure/utils';
import { tokenService } from '../../infrastructure/token';
import { sendSmsSignUp, sendSmsPasswordRetrieve } from '../../infrastructure/sendsms';
import { findOne, redis, findAll, trans, findById, create, deleteOne, modifyInstanceProperties, validator } from "../../infrastructure/db";
import { User, Appointment } from './auth.model';
import env from '../../config/env';

const crypto = require('crypto');
const Task = require('data.task');
const Maybe = require('data.maybe');
const { chain, map, ap, liftA2, mjoin, I } = require('pointfree-fantasy');
const { curry, pipe, apply, pick, values, converge, tap, prop, path, props, view, lensPath, lensProp, lensIndex, _, set, ifElse, isNil, forEach, reject, find, propEq, length, isArrayLike, omit, groupBy, merge } = require('ramda');
const uuid = require('uuid');

const captcha = pipe(
    Maybe.fromNullable,
    chain(info => new Task((rej, res) => {
        if (validator.matches(info.username, /^1\d{10}$/)) {
            res(info);
        } else {
            rej({ code: 404, message: 'invalid username' });
        }
    })),
    chain(info => info.retrieve ? sendSmsPasswordRetrieve(info.username) : sendSmsSignUp(info.username)),
)

const createUser = (info) => new Task((rej, res) => {
    trans()(async () => {
        let userId: string = uuid.v4();
        let passwordHash = crypto
            .createHash('md5')
            .update(userId)
            .update(info.password)
            .digest('hex');
        await User.create({ id: userId, username: info.username, passwordHash: passwordHash, identity: 'service' });
        // await Profile.create({ id: userId, userId: userId });
    })().then(res).catch(rej)
})

// compareCaptcha :: string -> string -> Task err void
const compareCaptcha = (body) => pipe(
    Maybe.fromNullable,
    chain(info => getCaptcha(info.username)),
    chain(info => new Task((rej, res) => {
        if (info == body.captcha) {
            res(body)
        } else {
            rej({ code: 404, message: 'invalid message' })
        }
    })),
)(body)

// deleteCaptcha :: string -> Task err void
const deleteCaptcha = (principal: string, prefix: string = 'username'): any => new Task((rej, res) => redis.del(`${prefix}-${principal}`).then(res).catch(rej));

const getCaptcha = (principal: string): any => new Task((rej, res) => redis.get(`username-${principal}`).then(res).catch(rej));

const register = (body) => pipe(
    Maybe.fromNullable,
    chain(info => findOne(User, { where: { username: info.username } })),
    chain(rejectNotNull({ code: 404, message: 'user has been exist！' })),
    chain(() => compareCaptcha(body)),
    chain(() => createUser(body))
)(body)

const verifyPassword = (info) => new Task((rej, res) => {
    User.findOne({ where: { username: info.username } }).then(result => {
        if (!!result) {
            if (result.identity === 'service') {
                let passwordHash = crypto
                    .createHash('md5')
                    .update(result.id)
                    .update(info.password)
                    .digest('hex');
                if (passwordHash === result.passwordHash) {
                    res({ userId: result.id });
                } else {
                    rej({ code: 404, message: 'invalid password' })
                }
            } else {
                rej({ code: 404, message: 'invalid account' })
            }
        } else {
            rej({ code: 404, message: 'account is not service' })
        }
    })
})
const verifyLogin = pipe(
    Maybe.fromNullable,
    chain(info => verifyPassword(info)),
    chain(info => fetchToken(info)),
)

const updateUser: any = (info) => new Task((rej, res) => {
    trans()(async () => {
        let result = await User.findOne({ where: { username: info.username } });
        if (!!result) {
            let passwordHash = crypto
                .createHash('md5')
                .update(result.id)
                .update(info.password)
                .digest('hex');
            await User.update({ passwordHash: passwordHash }, { where: { id: result.id, username: info.username } });
        } else {
            rej({ code: 404, message: 'user not exist' });
        }
    })().then(res).catch(rej)
})

const getBackPassword = (body) => pipe(
    Maybe.fromNullable,
    chain(() => compareCaptcha(body)),
    chain(() => updateUser(body)),
)(body)

// fetchToken :: any -> Task err {token: string}
const fetchToken = ({ userId }): any => Task.of(tokenService.sign({ userId }))
    .chain(token => Task.of({ token, userId }));

const tokenResponseGenerator = {
    dev: ({ token, userId }) => ({ token, userId }),
    prod: ({ token, userId }) => ({ token }) // omit userId
};

const generateTokenResponse = tokenResponseGenerator[env.token.factoryName] || tokenResponseGenerator['prod'];

const authMiddleware = (req, res, next) => {
    (async () => {
        req.query = Object.assign({}, req.query);
        const { authorization } = req.headers;
        const token = tokenService.getBearerToken(authorization);
        const decoded = token && await tokenService.verifyToken(token);
        const user = decoded && await User.findById(decoded.userId); // 开销？
        if (req.originalUrl == "/") { return next() }//当访问地址是根地址的时候，不验证。
        if (req.originalUrl.indexOf('auth') < 0) {
            console.log(`userId:${decoded.userId}`)
            if (!token) return res.status(401).json({ data: { redirect: '/login.html' } }); // need Authorization
            if (!decoded || !user) return res.status(401).end(); // token 无效 则返回登录界面
            req.query = merge({ userId: decoded.userId }, req.query || {});
        } else {
            if (user && req.originalUrl.indexOf('login') >= 0 && req.method.toLocaleLowerCase() === 'get')
                return res.json({ data: { redirect: '/index' } });
        }
        next();
    })();
}

const appointmentClass = pipe(
    Maybe.fromNullable,
    map(pick(['name', 'phone', 'referral', 'gradeType', 'grade', 'subject'])),
    chain(info => create(Appointment, info))
)

export {
    register,
    verifyLogin,
    getBackPassword,
    captcha,
    authMiddleware,
    appointmentClass
}